/*
 * SynchronizedRegister.java   2008/07/28
 *
 * Copyright 2008 Web & Zen Multimedia, All rights reserved.
 * Use is subject to license terms.
 */
package it.wzm.lightweightlog;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Properties;

/**
 * Default implementation of Register, it simply extends Register and store passed Dumper
 * into an ArrayList, called slot.
 */
class SynchronizedRegister<T extends Dumper> extends Register<T> implements Serializable {

	/** As required by serialization */
	private static final long serialVersionUID = -1107606779436763082L;
    /** Where all the Dumper are stored */
    private final LinkedList<T> reg;
    /** Threads holder */
    private final LinkedList<Thread> threads;    
    /** Failed reference */
    private final ArrayList dead;
    /** Holder for parallel */
    private boolean parallel;
    /** Switch for dumping */
    private boolean dumping;
    
    /**
     * Create register from a given Array of Dumper, null array cause ObjectInitializationException.
     * If in the passed array some Dumper index is null.
     * @param t Array of dumper
     */
    public SynchronizedRegister(Properties p, ArrayList<T> t) {
        // doing our Duty...
        super(p, t);
        // init object...
    	this.reg = new LinkedList<T>();
    	this.dead = new ArrayList();
    	this.threads = new LinkedList<Thread>();
        
    	// decide if we are parallel
    	try { 
    		this.parallel=Boolean.parseBoolean(this.get("parallel")); 
    	} catch (Exception e) {
    		this.parallel=Constant.DEFAULT_REGISTER_PARALLEL;
    	}
    	
        // browse our dumper array
        // and store each Dumper in register
        for (T x: t) {
            // check if our dumper is null
            if (x == null) {
                //LogFactory.getLog().serviceLog("Cannot add null");
            	//logger.error("Cannot add null");
                continue;
            }
            // and then...
            if (!this.save(x)) {
                //LogFactory.getLog().serviceLog("Couldn't save " + t.toString());
            	//logger.error("Couldn't save " + t);
            }
        }
        this.setStatus(Status.RUNNING);
    }
    
    /**
     * Save a passed Dumper into the slot, if the slot already contains this Dumper,
     * then nothing is stored and false is returned.
     * @param t Anything extending Dumper
     * @return true if Dumper is properly added false if not
     */
    protected synchronized boolean save(T t) {
        try {
        	return reg.add(t);
        } finally {
        	this.notifyAll();
        }
    }

    /**
     * Remove a passed Dumper from the slot, if the slot already does not contains this Dumper,
     * or the Dumper status is not FAILURE then nothing is removed and false is returned.
     * @param t Anything extending Dumper
     * @return true if Dumper is properly added false if not
     */
    protected synchronized boolean remove(T t) {
    	try {
	        // easy check...
	        if (!reg.contains(t)) {
	            return false;
	        // only Dumper with status FAILURE can be removed
	        }
	        if (t.getStatus() == Dumper.Status.FAILURE) {
	            return reg.remove(t);
	        } else {
	            return false;
	        }
    	} finally {
    		this.notifyAll();
    	}
    }

    /**
     * The size of the slot
     * @return int
     */
    @Override
    public int size() {
        return reg.size();
    }
    
    /**
     * Tell if we want to launch each dumper in a separate process 
     * @return true/false
     */
    public boolean isParallel() {
    	return this.parallel;
    }

	/**
	 * Tell if we are dumping
	 * @return the dumping
	 */
	public final boolean isDumping() {
		return dumping;
	}

	/**
     * Dispatch entries to each Dumper in slot
     * @param ll A LinkedList of Entry
     */
    protected synchronized <E extends Entry> void dispatchToDumper(LinkedList<E> ll) {
    	
    	// simple check...
    	if (ll == null || ll.isEmpty()) {
    		return;
    	}
    	
    	// only one dumping at a time is allowed...
    	if (this.isDumping()) {
    		try {        		
				wait();
			} catch (InterruptedException e) {
				// we cannot insert E
				return;
			}
    	}
    	
        // browse each Dumper
        dumping:
        for (T t:reg) {
        	
            // we launch each dump process in a separate thread
            if (this.isParallel()) {		        
		        // we now implement and start each Wrapper and add to a list...
		        Thread td = new Thread(new Wrapper(t,ll));
		        td.setName("dumperThread::"+ t.getName());
		        td.setDaemon(false);
		        threads.add(td);
		        td.start();
	            
		        // iterate and perform join() action
		        for (Thread it: threads) {
		        	try {
						it.join();
					} catch (InterruptedException e) {
						//LogFactory.getLog().error(e);
						//logger.error(e);
					}
		        }
		        
		        // cleaning resources...
		        threads.clear();	        
	        
	        // we have instead to call dumper serial mode
	        // which is...when one has finished, we start with next dumper...
            } else {
            	
	            try {
	                // opening            	
	            	t.open();
	                // browse each Entry in list
	                for (Entry e: ll)  t.dump(e);
                
	            } catch (DumperException ex) {
	                if (t.hasKillOnFailure()) {
	                	t.kill();
	                	dead.add(t);
	                } else continue dumping;
	                
	            } finally {
	                // finally closing...
	                try {
						t.close();
					} catch (DumperException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
	            }
            }
        }
    	
    	// clearing...
    	ll.clear();
    	
    	// cleaning register from failed Dumper
    	for (Object d: dead) {
    		// cleaning...
    		((Dumper)d).kill();
    		// remove and try to restore from backup....
    		reg.remove(d);
    		reg.add(
    				((T)LogUtil.restore( ((T)d).getName() ))
    				);
    	}
    	// clear...
    	dead.clear();
    	
    	this.notifyAll();
    }

    /**
     * Dispatch a single Entry to all Dumper in the slot
     * @param e Anything extending Entry
     */
    protected synchronized <E extends Entry> void dispatchToDumper(E e) {
        // browse each Dumper
        for (T t: reg) {
            try {
                // the following is normal routine
                t.open();
                t.dump(e);
            } catch (DumperException ex) {
                if (t.hasKillOnFailure()) {
                	t.kill();
                	dead.add(t);
                }
            } finally {
            	try {
					t.close();
				} catch (DumperException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
            	this.notifyAll();
            }
        }
    }

    /**
     * Close this Register and all Dumper in it 
     */
    @Override
	public void kill() {
        // browse each Dumper
        for (T t: reg) {
                t.kill();
        }
        reg.clear();        
    }
    

    // this class will act as a ThreadWrapper
    // for starting each dumper independently 
    // from the other...
    private class  Wrapper implements Runnable {
    	
    	// this will hold our Dumper
    	private final Dumper wd;
    	// this will hold our list of Entry
    	private final LinkedList<? extends Entry> wll;
    	
    	// the only constructor
    	public Wrapper(Dumper d, LinkedList<? extends Entry> ll) {
    		this.wd=d;
    		this.wll=ll;
    	}
   	
    	/** Required by runnable */
    	public void run() {
            try {
                // opening dumper and 
            	// browsing for each Entry 
                this.wd.open();        		
                for (Entry e: wll) this.wd.dump(e);

            } catch (DumperException ex) {     	
                if (wd.hasKillOnFailure()) {
                	wd.kill();
                	dead.add(wd);
                }
            } finally {
                try {
                    // finally closing...
                    this.wd.close();
                } catch (DumperException ex) {
                	//logger.error(ex);
                	//LogFactory.getLog().error("Cannot close Dumper: " + ex.getMessage());
                }
            }
    	}
    }    
    
    
}

